import dmdb from 'dmdb';
import db, { DB, Initializable } from './db';

class DMConnection extends Initializable implements db.IConnection {
    private connection: dmdb.Connection;

    constructor(connection: dmdb.Connection) {
        super();
        this.connection = connection;
    }

    isInitialized(): boolean {
        return this.connection !== undefined;
    }

    async ping(): Promise<void> {
        if (!this.connection) {
            throw new Error("Connection is not initialized.");
        }
        return this.connection.ping();
    }
    async execute<T>(sql: string, params?: db.BindParameters, resultSet?: boolean): Promise<db.Result<T>> {
        if (!this.connection) {
            throw new Error("Connection is not initialized.");
        }
        return this.connection.execute<T>(sql, params || [], { resultSet: resultSet || false });
    }
    async commit(): Promise<void> {
        if (!this.connection) {
            throw new Error("Connection is not initialized.");
        }
        return this.connection.commit();
    }
    async rollback(): Promise<void> {
        if (!this.connection) {
            throw new Error("Connection is not initialized.");
        }
        return this.connection.rollback();
    }
    
    async close(): Promise<void> {
        if (!this.connection) {
            throw new Error("Connection is not initialized.");
        }
        return this.connection.close();
    }
}

export class DMPool extends Initializable implements db.IPool {
    private pool?: dmdb.Pool;
    private readonly url: string;
    private readonly username: string;
    private readonly password: string;

    constructor(url: string, username: string, password: string) {
        super();
        this.url = url;
        this.username = username;
        this.password = password;
        this.initPool(url, username, password);
    }

    isInitialized(): boolean {
        return this.pool !== undefined;
    }

    async initPool(url: string, username: string, password: string) {
        this.pool = await dmdb.createPool({
            connectString: `dm://${username}:${password}@${url}?autoCommit=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true`,
            poolMax: 10,
            poolMin: 1
        });
        if (!this.pool) {
            throw new Error("Failed to create connection pool.");
        }
    }

    async close(drainTime?: number): Promise<void> {
        if (!this.pool) {
            throw new Error("Connection pool is not initialized.");
        }
        return this.pool.close(drainTime);
    }

    async getConnection(): Promise<db.IConnection> {
        if (!this.pool) {
            throw new Error("Connection pool is not initialized.");
        }
        const connection = await this.pool.getConnection();
        return new DMConnection(connection);
    }
}

export class DMDB extends DB {

    async setSchema(schemaName: string): Promise<void> {
        await this.execute(`ALTER SESSION SET CURRENT_SCHEMA = ${schemaName}`);
    }
    async getSchemaNames(): Promise<string[]> {
        const result = await this.execute<any>("select distinct object_name from SYS.ALL_OBJECTS where OBJECT_TYPE = 'SCH'");
        return result.rows?.map(row => row[0]) || [];
    }

    async getTableNames(schemaName: string): Promise<string[]> {
        const result = await this.execute<any>(`select table_name from SYS.ALL_TABLES where OWNER = '${schemaName}'`);
        return result.rows?.map(row => row[0]) || [];
    }

    async getTableDDL(schemaName: string, tableName: string): Promise<string> {
        const result = await this.execute<any>(`select DBMS_METADATA.GET_DDL('TABLE', '${tableName}', '${schemaName}') from dual`);
        return result.rows?.map(row => row[0])[0].iLob.data || '';
    }
}


export async function getDB(url: string, username: string, password: string): Promise<DB> {
    const pool = new DMPool(url, username, password);
    await pool.awaitInitialized();
    const db = new DMDB(pool);
    await db.awaitInitialized();
    return db;
}
